﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using MoveTheCar.Model;
using MoveTheCar.Utilities;
using MoveTheCar.View;

namespace MoveTheCar.ViewModel
{
    public class MainViewModel : INPC
    {
        #region Members
        private MainWindow view;
        private DifficultyWindow levelView;
        private ObservableCollection<Car> cars;
        private double playGridHeight;
        private double playGridWidth;
        private int difficulty;
        #endregion Members

        #region Constructor
        public MainViewModel(Window window, Window window2)
        {
            levelView = (DifficultyWindow)window;

            view = (MainWindow)window2;
            view.SizeChanged += new SizeChangedEventHandler(Resize);

            FinishedCars = new ObservableCollection<Car>();

        }
        #endregion Constructor

        #region Databinded Methods
        public ObservableCollection<Car> Cars
        {
            get
            {
                if (cars == null)
                    cars = new ObservableCollection<Car>();

                return cars;
            }
        }
        private ObservableCollection<Car> FinishedCars { get; set; }
        private Boolean IsAnimating { get; set; }

        public double PlayGridHeight
        {
            get { return playGridHeight; }
            set
            {
                if (value == playGridHeight)
                    return;

                playGridHeight = value;
                RaisePropertyChanged("PlayGridHeight");
            }
        }

        public double PlayGridWidth
        {
            get { return playGridWidth; }
            set
            {
                if (value == playGridWidth)
                    return;

                playGridWidth = value;
                RaisePropertyChanged("PlayGridWidth");
            }
        }

        public string Instructions
        {
            get
            {
                return CheckIfPlayerIsVictorious() ? "You solved the puzzle.\nQuit to change difficulty\nNext Puzzle for another puzzle." : "To win, move the red cars to the black area." ;
            }
        }

        #region Puzzle generation methods
        public double GridSquareBaseLength { get; set; } // pixel length of a grid square
        public int GridSizeH { get; set; } // number of grid squares in height
        public int GridSizeW { get; set; } // number of grid squares in width
        public double GridX { get; set; } // starting position of the grid (x)
        public double GridY { get; set; } // starting position of the grid (y)
        public double GridSpacing { get; set; } // pixel length of the space between grid squares
        public double CarBaseLength { get; set; } // pixel length of the car (or half of it)
        #endregion Puzzle generation methods
        #endregion Databinded Methods

        #region Command Methods
        private ICommand newPuzzle;
        public ICommand NewPuzzle
        {
            get
            {
                if (newPuzzle == null)
                    newPuzzle = new RelayCommand(SetupGame);

                return newPuzzle;
            }
        }

        private ICommand quit;
        public ICommand Quit
        {
            get
            {
                if (quit == null)
                    quit = new RelayCommand(GotoLevelSelection);

                return quit;
            }
        }

        private ICommand tutCommand;
        public ICommand TutCommand
        {
            get
            {
                if (tutCommand == null)
                    tutCommand = new RelayCommand(e => SetupGame(0));

                return tutCommand;
            }
        }
        private ICommand easyCommand;
        public ICommand EasyCommand
        {
            get
            {
                if (easyCommand == null)
                    easyCommand = new RelayCommand(e => SetupGame(1));

                return easyCommand;
            }
        }
        private ICommand mediumCommand;
        public ICommand MediumCommand
        {
            get
            {
                if (mediumCommand == null)
                    mediumCommand = new RelayCommand(e => SetupGame(2));

                return mediumCommand;
            }
        }
        private ICommand hardCommand;
        public ICommand HardCommand
        {
            get
            {
                if (hardCommand == null)
                    hardCommand = new RelayCommand(e => SetupGame(3));

                return hardCommand;
            }
        }
        #endregion Command Methods

        #region Methods
        public void GotoLevelSelection(object param)
        {
            levelView.Show();
            view.Hide();
        }

        /// <summary>
        /// Issues the necessary commands to start the game.
        /// </summary>
        /// <param name="param">Contains the difficulty</param>
        public void SetupGame(object param)
        {
            // First clean the variables (Cars & FinishedCars)
            Cars.Clear();
            FinishedCars.Clear();
            difficulty = param != null ? (int)param : difficulty; // Get the difficulty, or keeps it if it doesn't change
            SetupPlayGrid(); // Setup the view

            ObservableCollection<Car> tempCars = PuzzleGenerator.GeneratePuzzle(
                difficulty,
                GridSquareBaseLength,
                GridSizeH, GridSizeW,
                GridX, GridY,
                GridSpacing, CarBaseLength);

            foreach (Car car in tempCars)
                Cars.Add(car);

            view.Show();
            levelView.Hide();
            RaisePropertyChanged("Instructions");
        }

        /// <summary>
        /// Generates a new puzzle based on the difficulty level.
        /// </summary>
        /// <param name="difficulty">Difficulty level, 1 is easy, 3 hard. 0 is tutorial-ish</param>
        public void SetupPlayGrid()
        {
            if (difficulty < 0 || difficulty > 3) // Invalid argument
                return;

            GridX = 2;
            GridY = 2;
            GridSpacing = 1;

            #region Difficulty Switch
            switch (difficulty)
            {
                case 0: // Tutorial-ish
                    GridSizeH = 4;
                    GridSizeW = 4;
                    break;
                case 1: // Easy
                    GridSizeH = 5;
                    GridSizeW = 5;
                    break;
                case 2: // Medium
                    GridSizeH = 6;
                    GridSizeW = 6;
                    break;
                case 3: // Hard
                    GridSizeH = 8;
                    GridSizeW = 9;
                    break;
                default: // Error
                    GridSizeH = 1;
                    GridSizeW = 1;
                    break;
            }
            #endregion Difficulty Switch

            int tempGridSquareHeight = (int)((view.Height - 40) - (GridSizeH + 1) * GridSpacing) / GridSizeH;
            int tempGridSquareWidth = (int)((view.Width - 17) - (GridSizeW + 1) * GridSpacing) / (GridSizeW + 2);
            GridSquareBaseLength = (tempGridSquareHeight < tempGridSquareWidth
                ? tempGridSquareHeight : tempGridSquareWidth);

            PlayGridWidth = GridSquareBaseLength * GridSizeW + (GridSizeW + 1) * GridSpacing;
            PlayGridHeight = GridSquareBaseLength * GridSizeH + (GridSizeH + 1) * GridSpacing;
            CarBaseLength = GridSquareBaseLength - 2;
        }

        public void MoveCar(Point moveLoc, Rectangle shape)
        {
            if (shape.DataContext.GetType().ToString() == "MS.Internal.NamedObject")
                return;
            Car car = (Car)shape.DataContext;
            Point newLoc = GetNoCollisionPoint(car, moveLoc);
            car.X += (int)newLoc.X;
            car.Y += (int)newLoc.Y;

            if (car.X > PlayGridWidth + 2 * GridSpacing && !IsAnimating)
            {
                IsAnimating = true;
                DoubleAnimation anim = new DoubleAnimation(0.0, TimeSpan.FromSeconds(0.5));
                anim.Completed += delegate
                {
                    FinishedCars.Add(car);
                    Cars.Remove(car);
                    IsAnimating = false;
                    if (CheckIfPlayerIsVictorious())
                        RaisePropertyChanged("Instructions"); //Do win thingy
                };
                shape.BeginAnimation(Rectangle.OpacityProperty, anim);
            }
        }

        private bool CheckIfPlayerIsVictorious()
        {
            return FinishedCars.Count == PuzzleGenerator.NumMainCars;
        }

        private Point GetNoCollisionPoint(Car car, Point movePoint)
        {
            Car c = GetNearestCar(car, movePoint);
            if (c != car)
                if (car.Orientation == State.Horizontal)
                {
                    if (car.Y < c.Y + c.Height && car.Y + car.Height > c.Y)
                        if (car.X + movePoint.X <= c.X + c.Width && car.X > c.X)
                            return new Point(c.X + c.Width - car.X + 1, 0.0);
                        else if (car.X + car.Width + movePoint.X >= c.X && car.X < c.X)
                            return new Point(c.X - (car.X + car.Width) - 1, 0.0);
                }
                else
                {
                    if (car.X < c.X + c.Width && car.X + car.Width > c.X)
                        if (car.Y + movePoint.Y <= c.Y + c.Height && car.Y > c.Y)
                            return new Point(0.0, c.Y + c.Height - car.Y + 1);
                        else if (car.Y + car.Height + movePoint.Y >= c.Y && car.Y < c.Y)
                            return new Point(0.0, c.Y - (car.Y + car.Height) - 1);
                }

            return (car.Orientation == State.Vertical ? new Point(0.0, movePoint.Y) : new Point(movePoint.X, 0.0));
        }

        private Car GetNearestCar(Car originCar, Point movingPoint)
        {
            Car c = null;
            if (originCar.Orientation == State.Horizontal)
            {
                foreach (Car car in Cars)
                {
                    if (movingPoint.X > 0) // positive, moving to the right 
                    {
                        if (c == null)
                            c = new Car(false, State.Vertical, 1, (originCar.IsUser
                                ? PlayGridWidth + (2 * GridSquareBaseLength)
                                : PlayGridWidth), originCar.Y);
                        // make sure it's and invisible car at the view's border, except for the user car, it car move 1 car's length outside the grid.
                        if (car != originCar)
                            if (originCar.Y < car.Y + car.Height && originCar.Y + originCar.Height > car.Y)
                                if (originCar.X < car.X && car.X < c.X)
                                    c = car;
                    }
                    else // negative, moving to the left (or not moving)
                    {
                        if (c == null)
                            c = new Car(false, State.Vertical, 1, 1, originCar.Y); // make sure it's and invisible car at the view's border

                        if (car != originCar)
                            if (originCar.Y < car.Y + car.Height && originCar.Y + originCar.Height > car.Y)
                                if (originCar.X > car.X && car.X > c.X)
                                    c = car;
                    }
                }
            }
            else
            {
                foreach (Car car in Cars)
                {
                    if (movingPoint.Y > 0) // positive, moving to the bottom
                    {
                        if (c == null)
                            c = new Car(false, State.Horizontal, 1, originCar.X, PlayGridHeight); // make sure it's and invisible car at the view's border

                        if (car != originCar)
                            if (originCar.X < car.X + car.Width && originCar.X + originCar.Width > car.X)
                                if (originCar.Y < car.Y && car.Y < c.Y)
                                    c = car;
                    }
                    else // negative, moving to the top (or not moving)
                    {
                        if (c == null)
                            c = new Car(false, State.Horizontal, 1, originCar.X, 1); // make sure it's and invisible car at the view's border

                        if (car != originCar)
                            if (originCar.X < car.X + car.Width && originCar.X + originCar.Width > car.X)
                                if (originCar.Y > car.Y && car.Y > c.Y)
                                    c = car;
                    }
                }
            }

            return (c == null ? originCar : c);
        }
        #endregion Methods

        #region Events
        private void Resize(object sender, SizeChangedEventArgs e)
        {
            // TODO make play grid
            double oldPlayGridHeight = PlayGridHeight;
            double oldPlayGridWidth = PlayGridWidth;

            double tempGridSquareHeight = ((e.NewSize.Height - 40) - (GridSizeH + 1) * GridSpacing) / GridSizeH;
            double tempGridSquareWidth = ((e.NewSize.Width - 17) - (GridSizeW + 1) * GridSpacing) / (GridSizeW + 2); // with goal
            GridSquareBaseLength = (tempGridSquareHeight < tempGridSquareWidth
                ? tempGridSquareHeight : tempGridSquareWidth);

            CarBaseLength = GridSquareBaseLength - 2 * GridSpacing;
            PlayGridWidth = GridSquareBaseLength * GridSizeW + (GridSizeW + 1) * GridSpacing;
            PlayGridHeight = GridSquareBaseLength * GridSizeH + (GridSizeH + 1) * GridSpacing;

            foreach (Car car in Cars)
            {
                car.Resize(CarBaseLength);

                double newXtoPG = (car.X - (GridX + 1)) * PlayGridWidth / oldPlayGridWidth;
                double newYtoPG = (car.Y - (GridY + 1)) * PlayGridHeight / oldPlayGridHeight;

                car.X = GridX + GridSpacing + newXtoPG;
                car.Y = GridY + GridSpacing + newYtoPG;
            }
        }
        #endregion Events
    }
}
